--- kind: logging_system name: MicroPython logging 模块与 C 层输出机制 category: logging_system scope: - '**' source_files: - firmware/port/modules/logging.py - esp32s3/firmware/modules/logging.py - esp32s3/firmware/modules/uwebsockets/client.py - esp32s3/firmware/modules/uwebsockets/protocol.py - firmware/port/file_system/uwebsockets/client.py --- # 1. 使用的系统/方案 本仓库为基于 MicroPython 的掌控板固件,日志系统由两部分组成: - **Python 层**:在 `firmware/port/modules/logging.py` 和 `esp32s3/firmware/modules/logging.py` 中实现了 Python 标准库 `logging` 的一个精简子集(兼容 MicroPython 环境),提供 `Logger`、`getLogger`、`basicConfig`、`DEBUG/INFO/WARNING/ERROR/CRITICAL` 常量及 `debug/info/warning/error/critical/exc/exception` 方法。 - **C 层**:通过 `sys.stderr` 作为默认输出流,底层实际依赖 MicroPython 运行时将 `print()` / `_stream.write()` 重定向到 REPL 或串口调试输出;C 扩展 builtins 未实现独立的日志框架,仅通过 `sys.print_exception` 输出异常堆栈。 该实现是 MicroPython 官方 `logging` 模块的精简移植,并非自研框架。 # 2. 关键文件 | 文件 | 层级 | 作用 | |---|---|---| | `firmware/port/modules/logging.py` | Python 扩展模块(ESP32 端口) | 提供 `logging` 模块实现 | | `esp32s3/firmware/modules/logging.py` | Python 扩展模块(ESP32-S3 端口) | 同上,与 ESP32 端口完全一致 | | `esp32s3/firmware/modules/uwebsockets/client.py` | Python 扩展模块 | 使用 `logging.getLogger(__name__)` 记录 WebSocket 连接调试信息 | | `esp32s3/firmware/modules/uwebsockets/protocol.py` | Python 扩展模块 | 同上 | | `firmware/port/file_system/uwebsockets/*.py` | 文件系统 Python 模块 | 同上,用于 SD/Flash 上的用户代码 | # 3. 架构与约定 ## 日志级别 采用与 Python 标准库一致的数值定义:`NOTSET=0, DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50`。每个 `Logger` 实例维护自己的 `level`,若未设置则回退到模块级全局 `_level`(默认 `INFO`)。判断条件为 `level >= (self.level or _level)`。 ## 输出格式 每条日志输出形如 `LEVEL:name:msg`,其中 level 标签映射为 `CRIT/ERROR/WARN/INFO/DEBUG`,未知级别显示为 `LVL`。名称来自 `getLogger(name)` 传入的模块名或 `None`。 ## 输出目标 默认输出流 `_stream = sys.stderr`,可通过 `basicConfig(stream=...)` 替换。`filename` 和 `format` 参数被显式忽略并打印提示:`logging.basicConfig: filename arg is not supported` / `format arg is not supported`。 ## Logger 缓存 模块内维护 `_loggers = {}` 字典,`getLogger(name)` 按 name 复用 Logger 实例,避免重复创建。 ## 异常输出 `exc(e, msg)` 先以 ERROR 级别输出消息,再调用 `sys.print_exception(e, _stream)` 输出完整堆栈;`exception(msg)` 从当前异常上下文获取异常对象后委托给 `exc`。 ## 顶层便捷函数 `logging.info()` 和 `logging.debug()` 直接操作一个默认的 `getLogger(None)` 实例,便于快速输出。 # 4. 使用模式与约束 - **模块级 logger 模式**:`uwebsockets` 等模块通过 `LOGGER = logging.getLogger(__name__)` 获取命名 logger,并以 `__debug__` 条件开关控制 debug 级别输出,这是仓库中观察到的唯一使用方式。 - **无结构化字段**:日志输出仅为字符串拼接,不支持结构化字段(JSON、键值对等)。 - **无多 sink**:仅支持单一 `sys.stderr` 输出,无法同时写入文件、网络或 SPIFFS/LittleFS。 - **无异步/线程安全**:实现为同步单进程模型,未考虑并发保护。 - **C 层无独立日志**:C 扩展 builtins(`firmware/port/builtins/`、`esp32s3/firmware/builtins/`)未发现自定义日志宏或 `ESP_LOG*` 调用,C 层调试依赖 MicroPython 运行时的 `printf`/REPL 输出。 - **与 MicroPython 行为对齐**:该实现遵循 MicroPython 对 `logging` 的标准裁剪策略——保留核心 API,移除 handler/formatter 等重量组件。 # 5. 结论 仓库的日志系统是一个轻量级的 Python 层 `logging` 适配,面向资源受限的嵌入式 MicroPython 环境,输出统一走 `sys.stderr`(即串口/REPL)。它不具备生产级日志系统的特性(多 sink、结构化、异步、过滤链),但足以满足开发调试阶段按级别过滤的文本日志需求。